package com.wyfx.blockchain.dao.impl;

import com.wyfx.blockchain.dao.RocksDao;
import lombok.extern.log4j.Log4j2;
import org.rocksdb.Options;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import org.springframework.util.SerializationUtils;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.*;

/**
 * @Description
 * @Auther: wsm
 * @Date: 2020/9/3 14:42
 */
@Repository
@Log4j2
public class RocksDaoImpl implements RocksDao<String, Object> {

    private final static String FILE_NAME = "block-chain-rocksDB";

    @Value("${parent.dir}")
    private String parentDir;

    File baseDir;

    RocksDB db;

    /**
     * @desc 初始化创建rocksDB本地库文件
     * @Return void
     * @Author wsm
     * @date 2020/9/3 14:48
     */
    @PostConstruct
    void initialize() {
        RocksDB.loadLibrary();
        final Options options = new Options();
        options.setCreateIfMissing(true);
        try {
            Files.createDirectories(new File(parentDir).getAbsoluteFile().toPath());
            baseDir = new File(parentDir, FILE_NAME);
            Files.createDirectories(baseDir.getParentFile().toPath());
            Files.createDirectories(baseDir.getAbsoluteFile().toPath());
            db = RocksDB.open(options, baseDir.getAbsolutePath());
            log.info("RocksDB initialized");
        } catch (IOException | RocksDBException e) {
            log.error("Error initializing RocksDB. Exception: '{}', message: '{}'", e.getCause(), e.getMessage(), e);
        }
    }

    @Override
    public synchronized boolean save(String key, Object value) {
        log.info("saving value '{}' with key '{}'", value, key);
        try {
            db.put(key.getBytes(), SerializationUtils.serialize(value));
        } catch (RocksDBException e) {
            log.error("Error saving entry. Cause: '{}', message: '{}'", e.getCause(), e.getMessage());
            return false;
        }
        return true;
    }

    @Override
    public synchronized Optional<Object> find(String key) {
        Object value = null;
        try {
            byte[] bytes = db.get(key.getBytes());
            if (bytes != null) {
                value = SerializationUtils.deserialize(bytes);
            }
        } catch (RocksDBException e) {
            log.error(
                    "Error retrieving the entry with key: {}, cause: {}, message: {}",
                    key,
                    e.getCause(),
                    e.getMessage()
            );
        }
        log.info("finding key '{}' returns '{}'", key, value);
        return value != null ? Optional.of(value) : Optional.empty();
    }

    @Override
    public synchronized boolean delete(String key) {
        log.info("deleting key '{}'", key);
        try {
            db.delete(key.getBytes());
        } catch (RocksDBException e) {
            log.error("Error deleting entry, cause: '{}', message: '{}'", e.getCause(), e.getMessage());
            return false;
        }
        return true;
    }

    @Override
    public List<Map> list() {

        List<Map> list = new ArrayList<>();
        RocksIterator iter = db.newIterator();
        for (iter.seekToFirst(); iter.isValid(); iter.next()) {
            Map map = new HashMap(5);
            map.put("key", new String(iter.key(), Charset.defaultCharset()));
            map.put("value", new String(iter.value(), Charset.defaultCharset()));
            list.add(map);
        }
        return list;
    }
}
